package app

import (
	"context"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	"gitee.com/zawei/yggdrasil/pkg"
	"gitee.com/zawei/yggdrasil/pkg/defers"
	"gitee.com/zawei/yggdrasil/pkg/log"
	"gitee.com/zawei/yggdrasil/pkg/metrics"
	"gitee.com/zawei/yggdrasil/pkg/registry"
	"gitee.com/zawei/yggdrasil/pkg/sync/errgroup"
	"gitee.com/zawei/yggdrasil/pkg/transport/server"
)

type Stage uint32

const (
	StageMin Stage = iota
	//StageBeforeStop before app stop
	StageBeforeStop
	//StageAfterStop after app stop
	StageAfterStop

	StageMax
)

type Application struct {
	initOnce sync.Once
	runOnce  sync.Once
	stopOnce sync.Once

	registry registry.Registry

	hooks map[Stage]*defers.Defer
	eg    *errgroup.Group
}

func New(inits ...Option) *Application {
	app := &Application{
		eg: errgroup.WithCancel(context.Background()),
		hooks: map[Stage]*defers.Defer{
			StageBeforeStop: defers.NewDefer(),
			StageAfterStop:  defers.NewDefer(),
		},
	}
	for i := StageMin; i < StageMax; i++ {
		app.hooks[i] = defers.NewDefer()
	}
	for _, o := range inits {
		if err := o(app); err != nil {
			log.Fatalw("fault to init app option", "error", err)
		}
	}

	return app
}

func (app *Application) Init(opts ...Option) {
	app.initOnce.Do(func() {
		for _, o := range opts {
			if err := o(app); err != nil {
				log.Fatalw("fault to init application", "error", err)
			}
		}
	})
}

func (app *Application) Stop() error {
	var err error
	app.stopOnce.Do(func() {
		app.runHooks(StageBeforeStop)

		_ = app.registry.Close()

		g := errgroup.Group{}
		g.Go(app.stopServers)
		err = g.Wait()

		app.runHooks(StageAfterStop)
	})
	return err
}

func (app *Application) GracefulStop(ctx context.Context) error {
	var err error
	app.stopOnce.Do(func() {
		app.runHooks(StageBeforeStop)

		_ = app.registry.Close()

		g := errgroup.WithContext(ctx)
		g.Go(app.gracefulStopServers)
		err = g.Wait()

		app.runHooks(StageAfterStop)
	})
	return err
}

func (app *Application) Run() error {
	var err error
	app.runOnce.Do(func() {
		app.waitSignals()
		app.metric()

		app.eg.Go(app.startServers)
		if err = app.eg.Wait(); err != nil {
			log.Errorw("app shutdown with error", "error", err)
			return
		}
		log.Info("app shutdown")
	})

	return err
}

func (app *Application) metric() {
	err := metrics.CreateGauge(metrics.GaugeOpts{
		Namespace: pkg.FrameworkName(),
		Subsystem: pkg.AppName(),
		Name:      "build_info",
		Labels:    []string{"app_id", "app_version", "framework_version", "start_time", "build_time", "go_version"},
	})
	if err != nil {
		log.Fatal(err)
	}
	_ = metrics.GaugeSet("build_info", float64(time.Now().UnixNano()/1e6), map[string]string{
		"app_id":            pkg.AppName(),
		"app_version":       pkg.AppID(),
		"framework_version": pkg.AppVersion(),
		"start_time":        pkg.StartTime(),
		"build_time":        pkg.BuildTime(),
		"go_version":        pkg.GoVersion(),
	})
}

func (app *Application) runHooks(k Stage) {
	hooks, ok := app.hooks[k]
	if ok {
		hooks.Done()
	}
}

func (app *Application) startServers(ctx context.Context) error {
	eg := errgroup.WithContext(ctx)
	for _, s := range server.GetServers() {
		s := s
		eg.Go(func(ctx context.Context) (err error) {
			if err := s.Init(); err != nil {
				return err
			}
			info := s.Info()
			_ = app.registry.Register(ctx, info)
			defer app.registry.Deregister(ctx, info)
			log.Infow("server started", "scheme", info.Scheme, "addr", info.Address())
			err = s.Start()
			return
		})
	}
	return eg.Wait()
}

func (app *Application) stopServers(ctx context.Context) error {
	eg := errgroup.WithContext(ctx)
	for _, s := range server.GetServers() {
		s := s
		eg.Go(func(ctx context.Context) (err error) {
			err = s.Stop()
			return
		})
	}
	return eg.Wait()
}

func (app *Application) gracefulStopServers(ctx context.Context) error {
	eg := errgroup.WithContext(ctx)
	for _, s := range server.GetServers() {
		s := s
		eg.Go(func(ctx context.Context) (err error) {
			err = s.GracefulStop(ctx)
			return
		})
	}
	return eg.Wait()
}

func (app *Application) waitSignals() {
	sig := make(chan os.Signal, 2)
	signal.Notify(sig, shutdownSignals...)
	go func() {
		s := <-sig
		go func() {
			if s != syscall.SIGQUIT {
				if err := app.GracefulStop(context.TODO()); err != nil {
					log.Errorw("fault to graceful stop", "error", err)
					return
				}
			} else {
				if err := app.Stop(); err != nil {
					log.Errorw("fault to stop", "error", err)
					return
				}
			}
			log.Info("application stopped")
		}()
		<-sig
		os.Exit(128 + int(s.(syscall.Signal))) // second signal. Exit directly.
	}()
}
